package com.yml.ktbus.utils

import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.os.Bundle
import android.support.v4.content.FileProvider
import android.view.View
import android.view.inputmethod.InputMethodManager
import java.lang.reflect.InvocationTargetException

/**
 * from Github: https://github.com/Blankj/AndroidUtilCode
 * thanks Blankj
 */
class AppUtils private constructor() {
    class ActivityLifecycleImpl : ActivityLifecycleCallbacks {
        val activityList = mutableListOf<Activity>()
        private val mStatusListenerMap: MutableMap<Any, OnAppStatusChangedListener> = HashMap()
        private val mDestroyedListenerMap: MutableMap<Activity, MutableSet<OnActivityDestroyedListener>> =
            HashMap()
        private var mForegroundCount = 0
        private var mConfigCount = 0
        private var mIsBackground = false
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle) {
            setTopActivity(activity)
        }

        override fun onActivityStarted(activity: Activity) {
            if (!mIsBackground) {
                setTopActivity(activity)
            }
            if (mConfigCount < 0) {
                ++mConfigCount
            } else {
                ++mForegroundCount
            }
        }

        override fun onActivityResumed(activity: Activity) {
            setTopActivity(activity)
            if (mIsBackground) {
                mIsBackground = false
                postStatus(true)
            }
        }

        override fun onActivityPaused(activity: Activity) { /**/
        }

        override fun onActivityStopped(activity: Activity) {
            if (activity.isChangingConfigurations) {
                --mConfigCount
            } else {
                --mForegroundCount
                if (mForegroundCount <= 0) {
                    mIsBackground = true
                    postStatus(false)
                }
            }
        }

        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { /**/
        }

        override fun onActivityDestroyed(activity: Activity) {
            activityList.remove(activity)
            consumeOnActivityDestroyedListener(activity)
            fixSoftInputLeaks(activity)
        }

        val topActivity: Activity?
            get() {
                if (activityList.isNotEmpty()) {
                    val topActivity = activityList.last()
                    if (topActivity != null) {
                        return topActivity
                    }
                }
                val topActivityByReflect = topActivityByReflect
                topActivityByReflect?.let { setTopActivity(it) }
                return topActivityByReflect
            }

        fun addOnAppStatusChangedListener(
            `object`: Any,
            listener: OnAppStatusChangedListener
        ) {
            mStatusListenerMap[`object`] = listener
        }

        fun removeOnAppStatusChangedListener(`object`: Any) {
            mStatusListenerMap.remove(`object`)
        }

        fun removeOnActivityDestroyedListener(activity: Activity?) {
            if (activity == null) return
            mDestroyedListenerMap.remove(activity)
        }

        fun addOnActivityDestroyedListener(
            activity: Activity?,
            listener: OnActivityDestroyedListener?
        ) {
            if (activity == null || listener == null) return
            val listeners: MutableSet<OnActivityDestroyedListener>
            if (!mDestroyedListenerMap.containsKey(activity)) {
                listeners = HashSet()
                mDestroyedListenerMap[activity] = listeners
            } else {
                listeners = mDestroyedListenerMap[activity]!!
                if (listeners.contains(listener)) return
            }
            listeners.add(listener)
        }

        private fun postStatus(isForeground: Boolean) {
            if (mStatusListenerMap.isEmpty()) return
            for (onAppStatusChangedListener in mStatusListenerMap.values) {
                if (onAppStatusChangedListener == null) return
                if (isForeground) {
                    onAppStatusChangedListener.onForeground()
                } else {
                    onAppStatusChangedListener.onBackground()
                }
            }
        }

        private fun setTopActivity(activity: Activity) {
            if (PERMISSION_ACTIVITY_CLASS_NAME == activity.javaClass.name) return
            if (activityList.contains(activity)) {
                if (activityList.last() != activity) {
                    activityList.remove(activity)
                    activityList.add(activity)
                }
            } else {
                activityList.add(activity)
            }
        }

        private fun consumeOnActivityDestroyedListener(activity: Activity) {
            val iterator: MutableIterator<Map.Entry<Activity, Set<OnActivityDestroyedListener>>> =
                mDestroyedListenerMap.entries.iterator()
            while (iterator.hasNext()) {
                val (key, value) = iterator.next()
                if (key === activity) {
                    for (listener in value) {
                        listener.onActivityDestroyed(activity)
                    }
                    iterator.remove()
                }
            }
        }

        private val topActivityByReflect: Activity?
            private get() {
                try {
                    @SuppressLint("PrivateApi") val activityThreadClass =
                        Class.forName("android.app.ActivityThread")
                    val currentActivityThreadMethod =
                        activityThreadClass.getMethod("currentActivityThread").invoke(null)
                    val mActivityListField = activityThreadClass.getDeclaredField("mActivityList")
                    mActivityListField.isAccessible = true
                    val activities = mActivityListField[currentActivityThreadMethod] as Map<*, *>
                        ?: return null
                    for (activityRecord in activities.values) {
                        val activityRecordClass: Class<Any>? = activityRecord?.javaClass
                        val pausedField = activityRecordClass?.getDeclaredField("paused")
                        pausedField?.isAccessible = true
                        if (!pausedField?.getBoolean(activityRecord)!!) {
                            val activityField = activityRecordClass.getDeclaredField("activity")
                            activityField.isAccessible = true
                            return activityField[activityRecord] as Activity
                        }
                    }
                } catch (e: ClassNotFoundException) {
                    e.printStackTrace()
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                } catch (e: InvocationTargetException) {
                    e.printStackTrace()
                } catch (e: NoSuchMethodException) {
                    e.printStackTrace()
                } catch (e: NoSuchFieldException) {
                    e.printStackTrace()
                }
                return null
            }

        companion object {
            private fun fixSoftInputLeaks(activity: Activity?) {
                if (activity == null) return
                val imm = app!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                    ?: return
                val leakViews =
                    arrayOf("mLastSrvView", "mCurRootView", "mServedView", "mNextServedView")
                for (leakView in leakViews) {
                    try {
                        val leakViewField =
                            InputMethodManager::class.java.getDeclaredField(leakView)
                                ?: continue
                        if (!leakViewField.isAccessible) {
                            leakViewField.isAccessible = true
                        }
                        val obj = leakViewField[imm] as? View ?: continue
                        if (obj.rootView === activity.window.decorView.rootView) {
                            leakViewField[imm] = null
                        }
                    } catch (ignore: Throwable) { /**/
                    }
                }
            }
        }
    }

    class FileProvider4UtilCode : FileProvider() {
        override fun onCreate(): Boolean {
            init(context)
            return true
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // interface
    ///////////////////////////////////////////////////////////////////////////
    interface OnAppStatusChangedListener {
        fun onForeground()
        fun onBackground()
    }

    interface OnActivityDestroyedListener {
        fun onActivityDestroyed(activity: Activity?)
    }

    companion object {
        private const val PERMISSION_ACTIVITY_CLASS_NAME =
            "com.blankj.utilcode.util.PermissionUtils\$PermissionActivity"
        val activityLifecycle = ActivityLifecycleImpl()

        @SuppressLint("StaticFieldLeak")
        private var sApplication: Application? = null

        /**
         * Init utils.
         *
         * Init it in the class of Application.
         *
         * @param context context
         */
        fun init(context: Context?) {
            if (context == null) {
                init(applicationByReflect)
                return
            }
            init(context.applicationContext as Application)
        }

        /**
         * Init utils.
         *
         * Init it in the class of Application.
         *
         * @param app application
         */
        fun init(app: Application?) {
            if (sApplication == null) {
                if (app == null) {
                    sApplication = applicationByReflect
                } else {
                    sApplication = app
                }
                sApplication!!.registerActivityLifecycleCallbacks(activityLifecycle)
            } else {
                if (app != null && app.javaClass != sApplication!!.javaClass) {
                    sApplication!!.unregisterActivityLifecycleCallbacks(activityLifecycle)
                    activityLifecycle.activityList.clear()
                    sApplication = app
                    sApplication!!.registerActivityLifecycleCallbacks(activityLifecycle)
                }
            }
        }

        /**
         * Return the context of Application object.
         *
         * @return the context of Application object
         */
        val app: Application?
            get() {
                if (sApplication != null) return sApplication
                val app = applicationByReflect
                init(app)
                return app
            }
        private val applicationByReflect: Application
            private get() {
                try {
                    @SuppressLint("PrivateApi") val activityThread =
                        Class.forName("android.app.ActivityThread")
                    val thread = activityThread.getMethod("currentActivityThread").invoke(null)
                    val app = activityThread.getMethod("getApplication").invoke(thread)
                        ?: throw NullPointerException("u should init first")
                    return app as Application
                } catch (e: NoSuchMethodException) {
                    e.printStackTrace()
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                } catch (e: InvocationTargetException) {
                    e.printStackTrace()
                } catch (e: ClassNotFoundException) {
                    e.printStackTrace()
                }
                throw NullPointerException("u should init first")
            }
        val topActivityOrApp: Context?
            get() = if (isAppForeground) {
                val topActivity = activityLifecycle.topActivity
                topActivity ?: app
            } else {
                app
            }
        val isAppForeground: Boolean
            get() {
                val am = app!!.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
                    ?: return false
                val info = am.runningAppProcesses
                if (info == null || info.size == 0) return false
                for (aInfo in info) {
                    if (aInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        return aInfo.processName == app!!.packageName
                    }
                }
                return false
            }
    }

    init {
        throw UnsupportedOperationException("u can't instantiate me...")
    }
}